/*
 * gsession-inhibit.c -- command line utility to inhibit sleep via dbus calls to gnome-session-manager.
 *
 *  Created on: Oct 29, 2010
 *
 *  Author: Gordon Harris
 *
 *  Based on: test-inhibit.c from the Gnome Session Manager source code
 *
 *  Copyright (C) 2010 Gordon Harris.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 */



#include <stdlib.h>
#include <stdio.h>
#define __USE_GNU
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <time.h>

#include <glib.h>
#include <dbus/dbus-glib.h>

#include "gsession-inhibit.h"



static void LogMessage (LPAPPDATA lpAppData, char *szFormat, ...);
static void DispError (LPAPPDATA lpAppData, char *szFormat, ...);

/*
* 1: Inhibit logging out
* 2: Inhibit user switching
* 4: Inhibit suspending the session or computer
* 8: Inhibit the session being marked as idle
*/

static void DispSyntax( LPAPPDATA lpAppData ) {

	printf("%s version %s syntax:\n\n", lpAppData->szAppName, lpAppData->szAppVersion );

	printf("%s NUMBER[SUFFIX]...\n\n", lpAppData->szAppName);
	printf("  Inhibit gnome power management actions for NUMBER seconds.\n"
		   "  SUFFIX may be \'s\' for seconds (the default), \'m\' for minutes,\n"
		   "  \'h\' for hours or \'d\' for days.  NUMBER may be an arbitrary floating\n"
		   "  point number.\n\n");

	printf(	"Options include:\n"
			"  -h  --help            Displays syntax / help.\n"
			"  -v  --version         Displays version info.\n"
			"  -t  --type l|u|s|i    Type of inhibit request, any combination of:\n"
			"                        l (no logout); u (no user switch);\n"
			"                        s (no suspend); i (no session idle).\n"
			"                        The default is all, i.e. --type lusi.\n"
			"  -r  --reason \'REASON\' Reason for inhibiting sleep (default is 'not_idle').\n"
			"  -q  --quiet           Don\'t print messages to the terminal.\n"
			"  -d  --debug           Verbose messages.\n"
			"  -s  --save            Save NUMBER to ~/.gsession-inhibit\n"
			"  -l  --log [LOGFILE]   Copy messages to LOGFILE (default ==\n"
			"                        /var/log/gsession-inhibit.log).\n"
			"  -u  --utc --gmt       Timestamps in log as UTC rather than local time.\n"
			"      --display :n.n    DISPLAY environmental variable setting.\n"
			"                        Defaults to \":0\".\n"
			"\n\n");
}


static void DispVersion( LPAPPDATA lpAppData ) {
	printf("%s version %s, built %s.\n", lpAppData->szAppName, lpAppData->szAppVersion, __TIMESTAMP__);
}


char *GetTimeStr(LPAPPDATA lpAppData) {
	static char szTimeStr[80];
	time_t rawtime;
	struct tm * timeinfo;

	bzero(szTimeStr, 80);

	time ( &rawtime );

	if (lpAppData->uOpts & OPT_UTC)
		timeinfo = gmtime( &rawtime );
	else
		timeinfo = localtime ( &rawtime );

	//strftime (szTimeStr,80,"%c",timeinfo);
	strftime (szTimeStr,80,"%Y%m%d-%H%M%S",timeinfo);

	return szTimeStr;
}

static void LogMessage (LPAPPDATA lpAppData, char *szFormat, ...) {
	va_list args;
	FILE * pFile;
	int i = 0;

	if (lpAppData->uOpts & OPT_LOG) {
		pFile = fopen(lpAppData->szAppLogFile, "a");

		if (pFile) {
			fprintf(pFile, "%s  ", GetTimeStr(lpAppData));

			va_start (args, szFormat);

			i = vfprintf(pFile, szFormat, args);

			if (!strchr(szFormat, '\n')) {
				fputs("\n", pFile);
			}

			va_end (args);

			fclose(pFile);
		}

		if (!i) {
			//Turn off logging!!
			lpAppData->uOpts &= ~OPT_LOG;
			DispError(lpAppData, "Error: cannot write to log file %s", lpAppData->szAppLogFile);
			lpAppData->uError = ERROR_CANT_WRITELOG;
			//return FALSE;
		}
	}
}

static void DispError (LPAPPDATA lpAppData, char *szFormat, ...) {
	va_list args;
	FILE * pFile;
	int i = 0;


	if ( !(lpAppData->uOpts & OPT_QUIET) )  {
		va_start (args, szFormat);

		vfprintf(stderr, szFormat, args);

		if (!strchr(szFormat, '\n')) {
			fputs("\n", stderr);
		}

		va_end (args);

	}

	if (lpAppData->uOpts & OPT_LOG) {

		pFile = fopen(lpAppData->szAppLogFile, "a");

		if (pFile) {
			fprintf(pFile, "%s  ", GetTimeStr(lpAppData));

			va_start (args, szFormat);

			i = vfprintf(pFile, szFormat, args);

			if (!strchr(szFormat, '\n')) {
				fputs("\n", pFile);
			}

			va_end (args);

			fclose(pFile);
		}

		if (!i) {
			//Turn off logging!!
			lpAppData->uOpts &= ~OPT_LOG;
			DispError(lpAppData, "Error: cannot write to log file %s", lpAppData->szAppLogFile);
			lpAppData->uError = ERROR_CANT_WRITELOG;
			//return FALSE;
		}
	}
}


static void DispMessage (LPAPPDATA lpAppData, const char *szFormat, ...) {
	va_list args;
	FILE * pFile;
	int i = 0;

	if ( !(lpAppData->uOpts & OPT_QUIET) )  {
		va_start (args, szFormat);

		vfprintf(stdout, szFormat, args);

		if (!strchr(szFormat, '\n')) {
			fputs("\n", stdout);
		}

		va_end (args);
	}


	if (lpAppData->uOpts & OPT_LOG) {

		pFile = fopen(lpAppData->szAppLogFile, "a");

		if (pFile) {
			fprintf(pFile, "%s  ", GetTimeStr(lpAppData));

			va_start (args, szFormat);

			i = vfprintf(pFile, szFormat, args);

			if (!strchr(szFormat, '\n')) {
				fputs("\n", pFile);
			}

			va_end (args);

			fclose(pFile);
		}

		if (!i) {
			//Turn off logging!!
			lpAppData->uOpts &= ~OPT_LOG;
			DispError(lpAppData, "Error: cannot write to log file %s", lpAppData->szAppLogFile);
			lpAppData->uError = ERROR_CANT_WRITELOG;
			//return FALSE;
		}
	}
}


static void gsiMessage (LPAPPDATA lpAppData, const gchar *szFormat, ...) {
  va_list args;
  if ( !(lpAppData->uOpts & OPT_QUIET) )  {
	  va_start (args, szFormat);
	  g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, szFormat, args);
	  va_end (args);
  }
}


static void gsiWarn(LPAPPDATA lpAppData, const gchar *szFormat, ...) {
	  va_list args;

	  if (! (lpAppData->uOpts & OPT_QUIET) ) {
		  va_start (args, szFormat);
		  g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, szFormat, args);
		  va_end (args);
	  }
}


static void gsiDebug(LPAPPDATA lpAppData, const gchar *szFormat, ...) {
	  va_list args;

	  if (!(lpAppData->uOpts & OPT_QUIET)) {
		  va_start (args, szFormat);
		  g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, szFormat, args);
		  va_end (args);
	  }
}

static gboolean FileExists(char *szFilename) {

	return ( access( szFilename, F_OK ) != -1 );

}

static gboolean GetDefInhibitSecs (LPAPPDATA lpAppData) {

	FILE * pFile;
	int	i;

	//lpAppData->uInhibitCookie = 0;
	if (!FileExists(lpAppData->szAppDataFile)) {
		lpAppData->uInhibitSeconds = DEF_INHIBIT_TIME;
		if (!PutDefInhibitSecs(lpAppData)) {
			return FALSE;
		}
	}


	pFile = fopen(lpAppData->szAppDataFile, "r");

	if (!pFile) {
		DispError(lpAppData, "Error: cannot read file %s", lpAppData->szAppDataFile);
		lpAppData->uError = ERROR_CANT_READDEFSECS;
		return FALSE;
	}

	i = fscanf(pFile, "%u", &lpAppData->uInhibitSeconds);
	fclose(pFile);

	return (lpAppData->uInhibitSeconds > 0);
}


static gboolean PutDefInhibitSecs (LPAPPDATA lpAppData) {
	FILE * pFile;
	int i = 0;

	// have to have a valid value..
	if (!lpAppData->uInhibitSeconds > 0)
		return FALSE;

	pFile = fopen(lpAppData->szAppDataFile, "w");

	if (pFile) {
		i = fprintf(pFile, "%u", lpAppData->uInhibitSeconds);
		fclose(pFile);
	}

	if (!i) {
		DispError(lpAppData, "Error: cannot write file %s", lpAppData->szAppDataFile);
		lpAppData->uError = ERROR_CANT_SAVEDEFSECS;
		return FALSE;
	}

	return TRUE;
}


static gboolean SessionConnect (LPAPPDATA lpAppData) {

	if (lpAppData->dBusConnection == NULL) {
		lpAppData->gError = NULL;
		lpAppData->dBusConnection = dbus_g_bus_get (DBUS_BUS_SESSION, &lpAppData->gError );
		if (lpAppData->dBusConnection == NULL) {
			DispError (lpAppData, "Failed to connect to the session bus: %s",
					lpAppData->gError->message);
			g_error_free (lpAppData->gError);
			lpAppData->uError = ERROR_NODBUS;
			return FALSE;
		}
	}

	lpAppData->dBusProxySessionManager = dbus_g_proxy_new_for_name (lpAppData->dBusConnection,
										  SM_DBUS_NAME,
										  SM_DBUS_PATH,
										  SM_DBUS_INTERFACE);

	if (lpAppData->dBusProxySessionManager == NULL) {
		DispError (lpAppData, "Unable to connect to session manager");
		lpAppData->uError = ERROR_NOSESSION;
		return FALSE;
	}

	return TRUE;
}

static gboolean SessionDisconnect (LPAPPDATA lpAppData) {

	if (lpAppData->dBusProxySessionManager != NULL) {
		g_object_unref (lpAppData->dBusProxySessionManager);
		lpAppData->dBusProxySessionManager = NULL;
	}

	lpAppData->dBusConnection = NULL;

	return TRUE;
}


/*
static gboolean GetSessionInhibitStatus (LPAPPDATA lpAppData) {

	gboolean    res;
	GStrv name_list;


	name_list = NULL;

	res = dbus_g_proxy_call (lpAppData->dBusProxySessionManager,
							"GetInhibitors",
							G_TYPE_INVALID,
							//G_TYPE_ARRAY,
							G_TYPE_STRV,
                            &name_list,
							G_TYPE_INVALID);


	return TRUE;
}
*/

static gboolean SessionInhibit (LPAPPDATA lpAppData) {
	gboolean    res;
	const char *startup_id;
	guint       toplevel_xid;
	//guint       uFlags;

	startup_id = g_getenv ("DESKTOP_AUTOSTART_ID");

	toplevel_xid = 0;

	//uFlags = 1+2+4+8;

/*
	* 1: Inhibit logging out
    * 2: Inhibit user switching
    * 4: Inhibit suspending the session or computer
    * 8: Inhibit the session being marked as idle
*/

	//uFlags = (1 | 2 | 4 | 8);

	lpAppData->gError = NULL;

	res = dbus_g_proxy_call (lpAppData->dBusProxySessionManager,
							"Inhibit",
							&lpAppData->gError,
							G_TYPE_STRING, 	lpAppData->szAppID,
							G_TYPE_UINT, 	toplevel_xid,
							G_TYPE_STRING, 	lpAppData->szAppReason,
							G_TYPE_UINT, 	lpAppData->uInhibitFlags,
							G_TYPE_INVALID,
							G_TYPE_UINT, 	&lpAppData->uInhibitCookie,
							G_TYPE_INVALID);

	if (! res) {
		DispError(lpAppData, "Error: Inhibit failure. %s", lpAppData->gError->message);
		g_error_free (lpAppData->gError);
		lpAppData->uError = ERROR_CANT_INHIBIT;
		return FALSE;
	}


	//echo out the inhibiter cookie..
	if (lpAppData->uOpts & OPT_DEBUG) {
		DispMessage(lpAppData, "Inhibit 0x%08x granted for app_id: %s, reason: %s, type: 0x%08x for %u seconds..", lpAppData->uInhibitCookie, lpAppData->szAppID, lpAppData->szAppReason, lpAppData->uInhibitFlags, lpAppData->uInhibitSeconds);
	} else {
		DispMessage(lpAppData, "Inhibiting for %u seconds..", lpAppData->uInhibitSeconds);
	}

	return TRUE;
}



static gboolean SessionUninhibit (LPAPPDATA lpAppData) {
	gboolean res;

	lpAppData->gError = NULL;

	res = dbus_g_proxy_call (lpAppData->dBusProxySessionManager,
							"Uninhibit",
							&lpAppData->gError,
							G_TYPE_UINT, lpAppData->uInhibitCookie,
							G_TYPE_INVALID,
							G_TYPE_INVALID);

	if (! res) {
		DispError (lpAppData, "Failed to uninhibit: %s", lpAppData->gError->message);
		lpAppData->uError = ERROR_CANT_UNINHIBIT;
		g_error_free (lpAppData->gError);
		return FALSE;
	}


	if (lpAppData->uOpts & OPT_DEBUG) {
		DispMessage(lpAppData, "UnInhibiting session manager: 0x%08x", lpAppData->uInhibitCookie);
	} else {
		DispMessage(lpAppData, "Uninhibiting..");
	}

	lpAppData->uInhibitCookie = 0;

	return TRUE;
}


static gboolean GetOpts (LPAPPDATA lpAppData, int argc, char *argv[]) {
	char *lpPos;
	int n, nRet;

	//Zero the app data structure..
	bzero(lpAppData, sizeof(APPDATA));

	strcpy(lpAppData->szAppVersion, APP_VERSION);

	//Set paths & names..
	if (!readlink("/proc/self/exe", lpAppData->szAppPath, PATH_MAX-1)) {
		strcpy(lpAppData->szAppPath, argv[0]);
	}

	lpPos = strrchr(lpAppData->szAppPath, '/');
	if (lpPos) {
		strcpy(lpAppData->szAppName, ++lpPos);
	} else {
		strcpy(lpAppData->szAppName, argv[0]);
	}

	strcpy(lpAppData->szAppID, lpAppData->szAppName);
	strcat(lpAppData->szAppID, "-");
	//strcat(lpAppData->szAppID, g_getenv("USERNAME"));
	strcat(lpAppData->szAppID, getenv("USERNAME"));

	strcpy(lpAppData->szAppDataFile, g_getenv("HOME") );

	strcat(lpAppData->szAppDataFile, "/.");
	strcat(lpAppData->szAppDataFile, lpAppData->szAppName);

	//Set option defaults..
	lpAppData->uOpts |= ACT_INHIBIT;

	//Process the cmd line args..
	for (n = 1; n < argc; n++) {

		if ( (argv[n][0] == '-' && argv[n][1] != '-') || argv[n][0] == '/') {
			switch (tolower(argv[n][1])) {
				case 'h':
					lpAppData->uOpts |= ACT_HELP;
					lpAppData->uOpts &= ~ACT_INHIBIT;
					break;
				case 'v':
					lpAppData->uOpts |= ACT_VERSION;
					lpAppData->uOpts &= ~ACT_INHIBIT;
					break;
				case 't':
					if (n+1 < argc && *argv[n+1] != '-') {
						n++;
						if (strcasestr(argv[n], "l"))
							lpAppData->uInhibitFlags |= NO_LOGOUT;
						if (strcasestr(argv[n], "u"))
							lpAppData->uInhibitFlags |= NO_USERSWITCH;
						if (strcasestr(argv[n], "s"))
							lpAppData->uInhibitFlags |= NO_SUSPEND;
						if (strcasestr(argv[n], "i"))
							lpAppData->uInhibitFlags |= NO_IDLE;
					}
					break;
				case 'q':
					lpAppData->uOpts |= OPT_QUIET;
					//lpAppData->uOpts &= ~OPT_DEBUG;
					break;
				case 'd':
					lpAppData->uOpts |= OPT_DEBUG;
					//lpAppData->uOpts &= ~OPT_QUIET;
					break;
				case 's':
					lpAppData->uOpts |= OPT_SAVE;
					break;
				case 'l':
					lpAppData->uOpts |= OPT_LOG;
					if (n+1 < argc && *argv[n+1] != '-') {
						strcpy(lpAppData->szAppLogFile, argv[++n]);
					}
					break;
				case 'r':
					if (n+1 < argc && *argv[n+1] != '-') {
						strcpy(lpAppData->szAppReason, argv[++n]);
					}
					break;
				case 'u':
					lpAppData->uOpts |= OPT_UTC;
					break;
			}
		} else if (!strcasecmp(argv[n], "--help")) {
			lpAppData->uOpts |= ACT_HELP;
			lpAppData->uOpts &= ~ACT_INHIBIT;
		} else if (!strcasecmp(argv[n], "--version")) {
			lpAppData->uOpts |= ACT_VERSION;
			lpAppData->uOpts &= ~ACT_INHIBIT;
		} else if (!strcasecmp(argv[n], "--type")) {
			if (n+1 < argc && *argv[n+1] != '-') {
				n++;
				if (strcasestr(argv[n], "l"))
					lpAppData->uInhibitFlags |= NO_LOGOUT;
				if (strcasestr(argv[n], "u"))
					lpAppData->uInhibitFlags |= NO_USERSWITCH;
				if (strcasestr(argv[n], "s"))
					lpAppData->uInhibitFlags |= NO_SUSPEND;
				if (strcasestr(argv[n], "i"))
					lpAppData->uInhibitFlags |= NO_IDLE;
			}
		} else if (!strcasecmp(argv[n], "--display")) {
			if (n+1 < argc && *argv[n+1] != '-') {
				strcpy(lpAppData->szAppDispEnv, argv[++n]);
			}
		} else if (!strcasecmp(argv[n], "--quiet")) {
			lpAppData->uOpts |= OPT_QUIET;
		} else if (!strcasecmp(argv[n], "--debug")) {
			lpAppData->uOpts |= OPT_DEBUG;
		} else if (!strcasecmp(argv[n], "--save")) {
			lpAppData->uOpts |= OPT_SAVE;
		} else if (!strcasecmp(argv[n], "--utc")) {
			lpAppData->uOpts |= OPT_UTC;
		} else if (!strcasecmp(argv[n], "--gmt")) {
			lpAppData->uOpts |= OPT_UTC;
		} else if (!strcasecmp(argv[n], "--log")) {
			lpAppData->uOpts |= OPT_LOG;
			if (n+1 < argc && *argv[n+1] != '-') {
				strcpy(lpAppData->szAppLogFile, argv[++n]);
			}
		} else if (!strcasecmp(argv[n], "--reason")) {
			if (n+1 < argc && *argv[n+1] != '-') {
				strcpy(lpAppData->szAppReason, argv[++n]);
			}
		} else {
			//This must be the inhibit time
			//Only get this ONCE!
			if ( !lpAppData->uInhibitSeconds ) {
				lpAppData->cInhibitUnits = 's';
				nRet = sscanf(argv[n], "%f%c", &lpAppData->fInhibitDuration, &lpAppData->cInhibitUnits);
				if (nRet != EOF && nRet > 0) {
					switch (tolower(lpAppData->cInhibitUnits)) {
					case 'm':
						lpAppData->fInhibitDuration *= 60;
						break;
					case 'h':
						lpAppData->fInhibitDuration *= 3600;
						break;
					case 'd':
						lpAppData->fInhibitDuration *= 86400;
						break;
					}

					//No such thing as negative time in this continuum..
					if (lpAppData->fInhibitDuration < 0.0)
						lpAppData->fInhibitDuration = -(lpAppData->fInhibitDuration);

					lpAppData->uInhibitSeconds = (uint) (lpAppData->fInhibitDuration + 0.5);
				}

			}
		}

	}

	//Default the reason...
	if (!*lpAppData->szAppReason) {
		strcpy(lpAppData->szAppReason, DEF_REASON);
	}

	if (!*lpAppData->szAppLogFile) {
		sprintf(lpAppData->szAppLogFile, "/var/log/%s.log", lpAppData->szAppName);
	}

	//Default the reason...
	if (!*lpAppData->szAppDispEnv) {
		strcpy(lpAppData->szAppDispEnv, DEF_DISPLAY);
	}


	if ( !lpAppData->uInhibitSeconds && !GetDefInhibitSecs(lpAppData) ) {
		lpAppData->uInhibitSeconds = DEF_INHIBIT_TIME;
	}

	if ( !lpAppData->uInhibitFlags ) {
		lpAppData->uInhibitFlags = NO_LOGOUT | NO_USERSWITCH | NO_SUSPEND | NO_IDLE;
	}

	//Show the app data...
	if (lpAppData->uOpts & OPT_DEBUG) {

		DispError (lpAppData, "    AppName == %s", 		lpAppData->szAppName);
		DispError (lpAppData, "      AppID == %s", 		lpAppData->szAppID);
		DispError (lpAppData, "    AppPath == %s", 		lpAppData->szAppPath);
		DispError (lpAppData, "    AppData == %s", 		lpAppData->szAppDataFile);
		DispError (lpAppData, "     AppLog == %s", 		lpAppData->szAppLogFile);
		DispError (lpAppData, "  AppReason == %s", 		lpAppData->szAppReason);
		DispError (lpAppData, "    AppOpts == 0x%08x",	lpAppData->uOpts);
		DispError (lpAppData, "InhibitSecs == %d", 		lpAppData->uInhibitSeconds);
		DispError (lpAppData, "InhibitType == 0x%08x",	lpAppData->uInhibitFlags);
		DispError (lpAppData, "Display env == %s", 		lpAppData->szAppDispEnv);
	}

	if (lpAppData->uOpts & OPT_SAVE) {
		PutDefInhibitSecs(lpAppData);
	}

	//Set the DISPLAY environmental variable so that the app can connect to dbus..
	setenv("DISPLAY", lpAppData->szAppDispEnv, 1);

	return TRUE;
}


int main (int   argc, char *argv[]) {

	APPDATA AppData;
	int iAction;

	//Parse the cmd line args..
	if (!GetOpts(&AppData, argc, argv)) {
		exit(1);
	}

	g_type_init ();


	//Perform the action..
	iAction = AppData.uOpts & ACT_ACTIONMASK;

	switch (iAction) {
		case ACT_INHIBIT:
			if (!SessionConnect(&AppData)) {
				exit (AppData.uError);
			}

			if (!SessionInhibit(&AppData)) {
				SessionDisconnect(&AppData);
				exit (AppData.uError);
			}

			sleep(AppData.uInhibitSeconds);

			SessionUninhibit(&AppData);

			SessionDisconnect(&AppData);
			break;

		case ACT_HELP:
			DispSyntax(&AppData);
			break;

		case ACT_VERSION:
			DispVersion(&AppData);
			break;

	}


	return AppData.uError;

}
